/********************************************************************************
  DESCRIPTION OF THE FILE HERE
interfaccia per qualsiasi tipo di porta.
Per questo progetto si è pensato ad una socket perche' ...
Per semplicita' e' tutto su un file.
********************************************************************************/

#include "port.h"
#include "common.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include "request.h"
#include "response.h"
#include "pool.h" /* per la POOL_MAX_SIZE */

#define PORT_NUMBER_RAND rand()%65535 + 1
#define PORT_NUMBER PORT_NUMBER_RAND
#define QUEUE_SIZE 2*POOL_MAX_SIZE
#define MAX_BUF_REQUEST REQUEST_DATA_SIZE
#define MAX_BUF_RESPONSE RESPONSE_DATA_SIZE
#define SERVER_IP "127.0.0.1"

typedef struct SPort {  
  int sd;
} Port;

static void Port_init(ptrPort port){
  struct sockaddr_in addr;
  port->sd = socket(PF_INET, SOCK_STREAM, 0);
  throw_cond_error((port->sd < 0),
                   "Port_init",
                   "Error in socket creation"
                   );

  bzero((char *) &addr, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(PORT_NUMBER);
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  throw_cond_error((bind(port->sd, (struct sockaddr *)&addr, sizeof(addr)) < 0),
                   "Port_init",
                   "Error in socket binding"
                   );
  throw_cond_error((listen(port->sd, QUEUE_SIZE) != 0),
                   "Port_init",
                   "Error in starting the listening phase lato server"
                   );
}

ptrPort Port_create(){
  ptrPort port = new(Port);
  Port_init(port);
  return port;
}

/* Questa funzione ricava la richiesta dalla porta.
 * Se la porta non contiene richieste la funzione si blocca.
 */
void Port_manageRequest(ptrPort port, ptrPool pool){
  socklen_t addr_size;
  if (port == NULL) throw_error("Port_manageRequest", "null port");

  struct sockaddr_in client_addr;
  /* la accept e' bloccante  */
  addr_size = sizeof client_addr;
  int client_sd = accept(port->sd, (struct sockaddr *)&client_addr, &addr_size);
  throw_cond_error((client_sd < 0),
                   "Port_manageRequest",
                   "Error in connection acceptance"
                   );
  char buf[MAX_BUF_REQUEST];
  /* anche la recv sarebbe bloccante  */
  int len = recv(client_sd, buf, MAX_BUF_REQUEST, 0);
  throw_cond_error((len < 0),
                   "Port_manageRequest",
                   "Error in data retrieving"
                   );
  debug("I RECEIVED SOME DATA\n");

  ptrRequest request = Request_createFromDataStream(buf);
  debugInt("BEFORE CALLING Pool_assignSendAndCloseConnection", "client_sd", client_sd);
  Pool_assignSendAndCloseConnection(pool, request, client_sd);
  Request_destroy(request);
}

/* Deve essere bloccante fino alla soddisfazione della richiesta (RPC) */
ptrResponse Port_putRequestRPC(ptrRequest r){
  ptrResponse res;
  int len, err;
  void * buf;
  char buf2[MAX_BUF_RESPONSE];
  struct sockaddr_in server_addr;
  int client_sd = socket(PF_INET, SOCK_STREAM, 0);
  throw_cond_error((client_sd < 0),
                   "Port_putRequestRPC",
                   "Error in socket creation"
                   );
  /* non e' necessario fare bind() */

  memset((char *)&server_addr, 0, sizeof(server_addr));	/* azzera server_addr */
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(PORT_NUMBER);
  server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
  throw_cond_error((connect(client_sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0),
                   "Port_putRequestRPC",
                   "Error in connection attempt"
                   );  
  buf = Request_toDataStream(r);  

  /* la send e' bloccante */
  debug("I'M GONNA SEND MY REQUEST\n");
  err = send(client_sd, buf, MAX_BUF_REQUEST, 0);
  throw_cond_error((err < 0),
                   "Port_putRequestRPC",
                   "Error while sending data"
                   );

  /* la recv e' bloccante (corretto wrt RPC) */
  len = recv(client_sd, buf2, MAX_BUF_RESPONSE, 0);
  throw_cond_error((len < 0),
                   "Port_putRequestRPC",
                   "Error in data retrieving"
                   );
  debug("THE RESULT HAS GOT BACK\n");

  throw_cond_error((close(client_sd) < 0),
                   "Port_putRequestRPC",
                   "Error in connection closing"
                   );
  res = Response_createFromDataStream(buf2);
  return res;
}

#ifdef DEBUG
void Port_printf (ptrPort port){
  /* BLA, BLA, BLA */
}
#endif /* DEBUG */

static void Port_deinit(ptrPort port){
  throw_cond_error((close(port->sd) < 0),
                   "Port_deinit",
                   "Error in socket closing"
                   );
};

void Port_destroy(ptrPort port){
  if (port != NULL){
    Port_deinit(port);
    delete(port);
  }
}
